namespace Hack.Decompiling.PEA.PE
{
  using System;
  using System.IO;
  using System.Collections.Generic;
  using System.Text;

  [Flags]
  public enum SectionAttributes:uint
  {
    UNKNOWN                    =0x00000000,
    SCALE_INDEX                =0x00000001,  // Tls index is scaled
    TYPE_NO_PAD                =0x00000008,  // Reserved.
    TYPE_COPY                  =0x00000010,  // Reserved.

    CNT_CODE                   =0x00000020,  // Section contains code.
    CNT_INITIALIZED_DATA       =0x00000040,  // Section contains initialized data.
    CNT_UNINITIALIZED_DATA     =0x00000080,  // Section contains uninitialized data.

    LNK_OTHER                  =0x00000100,  // Reserved.
    LNK_INFO                   =0x00000200,  // Section contains comments or some other type of information.
    TYPE_OVER                  =0x00000400,  // Reserved.
    LNK_REMOVE                 =0x00000800,  // Section contents will not become part of image.
    LNK_COMDAT                 =0x00001000,  // Section contents comdat.
    NO_DEFER_SPEC_EXC          =0x00004000,  // Reset speculative exceptions handling bits in the TLB entries for this section.
    GPREL                      =0x00008000,  // Section content can be accessed relative to GP
    MEM_FARDATA                =0x00008000,
    SYSHEAP                    =0x00010000,
    MEM_PURGEABLE              =0x00020000,
    MEM_16BIT                  =0x00020000,
    MEM_LOCKED                 =0x00040000,
    MEM_PRELOAD                =0x00080000,

    ALIGN_1BYTES               =0x00100000,  //
    ALIGN_2BYTES               =0x00200000,  //
    ALIGN_4BYTES               =0x00300000,  //
    ALIGN_8BYTES               =0x00400000,  //
    ALIGN_16BYTES              =0x00500000,  // Default alignment if no others are specified.
    ALIGN_32BYTES              =0x00600000,  //
    ALIGN_64BYTES              =0x00700000,  //
    ALIGN_128BYTES             =0x00800000,  //
    ALIGN_256BYTES             =0x00900000,  //
    ALIGN_512BYTES             =0x00A00000,  //
    ALIGN_1024BYTES            =0x00B00000,  //
    ALIGN_2048BYTES            =0x00C00000,  //
    ALIGN_4096BYTES            =0x00D00000,  //
    ALIGN_8192BYTES            =0x00E00000,  //
    ALIGN_MASK                 =0x00F00000,

    LNK_NRELOC_OVFL            =0x01000000,  // Section contains extended relocations.
    MEM_DISCARDABLE            =0x02000000,  // Section can be discarded.
    MEM_NOT_CACHED             =0x04000000,  // Section is not cachable.
    MEM_NOT_PAGED              =0x08000000,  // Section is not pageable.
    MEM_SHARED                 =0x10000000,  // Section is shareable.
    MEM_EXECUTE                =0x20000000,  // Section is executable.
    MEM_READ                   =0x40000000,  // Section is readable.
    MEM_WRITE                  =0x80000000,  // Section is writeable.
    //Combined Attributes:
    TEXT                       =0x60000020,
    DATA                       =0xC0000040,
    RESOURCE                   =0x40000040,
    RELOCATION                 =0x42000040,
  }

  /// <summary>
  /// SectionHeader
  /// </summary>
  /// <remarks>
  ///  Section Name	Content	Characteristics
  ///.arch	Alpha architecture information	IMAGE_SCN_MEM_READ | IMAGE_SCN_CNT_INITIALIZED_DATA |          IMAGE_SCN_ALIGN_8BYTES | IMAGE_SCN_MEM_DISCARDABLE
  ///.bss	Uninitialized data	IMAGE_SCN_CNT_UNINITIALIZED_DATA | IMAGE_SCN_MEM_READ |                 IMAGE_SCN_MEM_WRITE
  ///.data	Initialized data	IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ |          IMAGE_SCN_MEM_WRITE
  ///.edata	Export tables	IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ
  ///.idata	Import tables	IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ |
  ///IMAGE_SCN_MEM_WRITE
  ///.pdata	Exception information	IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ
  ///.rdata	Read-only initialized data	IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ
  ///.reloc	Image relocations	IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ |                IMAGE_SCN_MEM_DISCARDABLE
  ///.rsrc	Resource directory	IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ |                 IMAGE_SCN_MEM_WRITE
  ///.text	Executable code	IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE |                 IIMAGE_SCN_MEM_READ
  ///.tls	Thread-local storage	IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ |                IMAGE_SCN_MEM_WRITE
  ///.xdata	Exception information	IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ
  /// </remarks>
  public unsafe struct SectionHeader
  {
    #region Statics
    public static SectionHeader it()
    {
      return new SectionHeader();
    }
    public static SectionHeader from(BinaryReader reader)
    {
      return it().Read(reader);
    }

    public static SectionHeader forText(UInt32 virtualAddress)
    {
      return define(TextSectionName,SectionAttributes.TEXT,
        virtualAddress);
    }
    public static SectionHeader forData(UInt32 virtualAddress)
    {
      return define(DataSectionName, SectionAttributes.DATA, virtualAddress);
    }
    public static SectionHeader forRelocation(UInt32 virtualAddress)
    {
      return define(RelocationSectionName, SectionAttributes.RELOCATION, virtualAddress);
    }
    public static SectionHeader forResource(UInt32 virtualAddress)
    {
      return define(ResourceSectionName, SectionAttributes.RESOURCE, virtualAddress);
    }
    public static SectionHeader forTLS(UInt32 virtualAddress)
    {
      return define(TLSSectionName,SectionAttributes.UNKNOWN, virtualAddress);
    }

    public static SectionHeader define(string sectionName, SectionAttributes attributes, UInt32 virtualAddress)
    {
      return it().Define(sectionName, attributes, virtualAddress);
    }
    public static SectionHeader define(string sectionName, SectionAttributes attributes, UInt32 virtualAddress, UInt32 virtualSize, UInt32 rawPointer, UInt32 rawSize)
    {
      return it().Define(sectionName, attributes, virtualAddress, virtualSize, rawPointer, rawSize);
    }

    #endregion
    #region Consts
    public const string TextSectionName = ".text";
    public const string DataSectionName = ".data";
    public const string RelocationSectionName = ".reloc";
    public const string ResourceSectionName = ".rsrc";
    public const string TLSSectionName = ".tls";

    public static readonly string[] KnownSectionNames =
    { 
        TextSectionName,
        DataSectionName,
        RelocationSectionName, 
        ResourceSectionName, 
        TLSSectionName 
    };


    public const int SizeOfShortName = 8;
    public const UInt32 DefaultRelocationAddress = 0x00000000;
    public const UInt16 DefaultRelocationCount = 0x00000000;
    public const UInt32 DefaultLinenumberAddress = 0x00000000;
    public const UInt16 DefaultLinenumberCount = 0x00000000;
    #endregion
    #region Fields
    public fixed Byte Name[SizeOfShortName];
    /// <summary>
    /// This field records the VirtualSize
    /// of the section in the executable files,
    /// the PhysicalAddress for object files otherwise
    /// </summary>
    public UInt32 VirtualSize;
    /// <summary>
    /// VirtualAddress is a 32-bit-value holding the RVA to 
    /// the section's data when it is loaded in RAM.
    /// </summary>
    public UInt32 VirtualAddress;
    /// <summary>
    /// SizeOfRawData is the size of the
    /// secion's data rounded up to the next multiple of 'FileAlignment'.
    /// </summary>
    public UInt32 SizeOfRawData;
    /// <summary>
    /// PointerToRawData is incredibly useful because
    /// it is the offset from the file's beginning to the section's data. 
    /// If it is 0, the section's data are not contained in the file and will be
    /// arbitrary at load time.
    /// </summary>
    public UInt32 PointerToRawData;
    /// <summary>
    /// This field is not used in executables.
    /// </summary>
    public UInt32 PointerToRelocations;
    /// <summary>
    /// This field is not used in executables.
    /// </summary>
    public UInt32 PointerToLinenumbers;
    /// <summary>
    /// This field is not used in executables.
    /// </summary>
    public UInt16 NumberOfRelocations;
    /// <summary>
    /// This field is not used in executables.
    /// </summary>
    public UInt16 NumberOfLinenumbers;
    /// <summary>
    /// Characteristics is a bunch of flags describing how the
    /// section's memory should be treated
    /// </summary>
    public UInt32 Characteristics;
    #endregion
    #region Internal Fields
    internal UInt32 LastAddress;
    #endregion
    #region Properties
    public string SectionName
    {
      get
      {
        fixed (Byte* name = this.Name)
        {
          string n = new string(' ', SizeOfShortName);
          char[] chars = new char[SizeOfShortName];
          for (int i = 0; i < SizeOfShortName; i++)
          {
            char c = (char)name[i];
            chars[i] = c == 0 ? ' ' : (char)c;
          }
          return new string(chars).TrimEnd();
        }
      }
      set
      {
        fixed (Byte* name = this.Name)
        {
          int len = value != null ? value.Length : 0;

          for (int i = 0; i < SizeOfShortName; i++)
          {
            name[i] = (i < len) ? (Byte)value[i] : (Byte)0;
          }
        }
      }
    }
    public SectionAttributes Attributes
    {
      get
      {
        return (SectionAttributes)this.Characteristics;
      }
      set
      {
        this.Characteristics = (UInt32)value;
      }
    }
    public bool IsIndexScaled
    {
      get
      {
        return (this.Attributes & SectionAttributes.SCALE_INDEX) != 0;
      }
      set
      {
        if (value)
        {
          this.Attributes |= SectionAttributes.SCALE_INDEX;
        }
        else
        {
          this.Attributes &= ~SectionAttributes.SCALE_INDEX;
        }
      }
    }
    public bool ContainsCode
    {
      get
      {
        return (this.Attributes & SectionAttributes.CNT_CODE) != 0;
      }
      set
      {
        if (value)
        {
          this.Attributes |= SectionAttributes.CNT_CODE;
        }
        else
        {
          this.Attributes &= ~SectionAttributes.CNT_CODE;
        }
      }
    }
    public bool ContainsInitializedData
    {
      get
      {
        return (this.Attributes & SectionAttributes.CNT_INITIALIZED_DATA) != 0;
      }
      set
      {
        if (value)
        {
          this.Attributes |= SectionAttributes.CNT_INITIALIZED_DATA;
        }
        else
        {
          this.Attributes &= ~SectionAttributes.CNT_INITIALIZED_DATA;
        }
      }
    }
    public bool ContainsUninitializedData
    {
      get
      {
        return (this.Attributes & SectionAttributes.CNT_UNINITIALIZED_DATA) != 0;
      }
      set
      {
        if (value)
        {
          this.Attributes |= SectionAttributes.CNT_UNINITIALIZED_DATA;
        }
        else
        {
          this.Attributes &= ~SectionAttributes.CNT_UNINITIALIZED_DATA;
        }
      }
    }
    public bool ContainsLinkInfo
    {
      get
      {
        return (this.Attributes & SectionAttributes.LNK_INFO) != 0;
      }
      set
      {
        if (value)
        {
          this.Attributes |= SectionAttributes.LNK_INFO;
        }
        else
        {
          this.Attributes &= ~SectionAttributes.LNK_INFO;
        }
      }
    }
    public bool ContainsLinkComdata
    {
      get
      {
        return (this.Attributes & SectionAttributes.LNK_COMDAT) != 0;
      }
      set
      {
        if (value)
        {
          this.Attributes |= SectionAttributes.LNK_COMDAT;
        }
        else
        {
          this.Attributes &= ~SectionAttributes.LNK_COMDAT;
        }
      }
    }
    public bool ContainsLinkRemoved
    {
      get
      {
        return (this.Attributes & SectionAttributes.LNK_REMOVE) != 0;
      }
      set
      {
        if (value)
        {
          this.Attributes |= SectionAttributes.LNK_REMOVE;
        }
        else
        {
          this.Attributes &= ~SectionAttributes.LNK_REMOVE;
        }
      }
    }
    public bool ContainsLinkRelocation
    {
      get
      {
        return (this.Attributes & SectionAttributes.LNK_NRELOC_OVFL) != 0;
      }
      set
      {
        if (value)
        {
          this.Attributes |= SectionAttributes.LNK_NRELOC_OVFL;
        }
        else
        {
          this.Attributes &= ~SectionAttributes.LNK_NRELOC_OVFL;
        }
      }
    }
    public bool ResetSpeculativeException
    {
      get
      {
        return (this.Attributes & SectionAttributes.NO_DEFER_SPEC_EXC) != 0;
      }
      set
      {
        if (value)
        {
          this.Attributes |= SectionAttributes.NO_DEFER_SPEC_EXC;
        }
        else
        {
          this.Attributes &= ~SectionAttributes.NO_DEFER_SPEC_EXC;
        }
      }
    }
    public bool ContainsRelativeGP
    {
      get
      {
        return (this.Attributes & SectionAttributes.GPREL) != 0;
      }
      set
      {
        if (value)
        {
          this.Attributes |= SectionAttributes.GPREL;
        }
        else
        {
          this.Attributes &= ~SectionAttributes.GPREL;
        }
      }
    }
    public bool IsDiscardable
    {
      get
      {
        return (this.Attributes & SectionAttributes.MEM_DISCARDABLE) != 0;
      }
      set
      {
        if (value)
        {
          this.Attributes |= SectionAttributes.MEM_DISCARDABLE;
        }
        else
        {
          this.Attributes &= ~SectionAttributes.MEM_DISCARDABLE;
        }
      }
    }
    public bool IsUncachable
    {
      get
      {
        return (this.Attributes & SectionAttributes.MEM_NOT_CACHED) != 0;
      }
      set
      {
        if (value)
        {
          this.Attributes |= SectionAttributes.MEM_NOT_CACHED;
        }
        else
        {
          this.Attributes &= ~SectionAttributes.MEM_NOT_CACHED;
        }
      }
    }
    public bool IsUnpagable
    {
      get
      {
        return (this.Attributes & SectionAttributes.MEM_NOT_PAGED) != 0;
      }
      set
      {
        if (value)
        {
          this.Attributes |= SectionAttributes.MEM_NOT_PAGED;
        }
        else
        {
          this.Attributes &= ~SectionAttributes.MEM_NOT_PAGED;
        }
      }
    }
    public bool IsShared
    {
      get
      {
        return (this.Attributes & SectionAttributes.MEM_SHARED) != 0;
      }
      set
      {
        if (value)
        {
          this.Attributes |= SectionAttributes.MEM_SHARED;
        }
        else
        {
          this.Attributes &= ~SectionAttributes.MEM_SHARED;
        }
      }
    }
    public bool IsExecutable
    {
      get
      {
        return (this.Attributes & SectionAttributes.MEM_EXECUTE) != 0;
      }
      set
      {
        if (value)
        {
          this.Attributes |= SectionAttributes.MEM_EXECUTE;
        }
        else
        {
          this.Attributes &= ~SectionAttributes.MEM_EXECUTE;
        }
      }
    }
    public bool IsReadable
    {
      get
      {
        return (this.Attributes & SectionAttributes.MEM_READ) != 0;
      }
      set
      {
        if (value)
        {
          this.Attributes |= SectionAttributes.MEM_READ;
        }
        else
        {
          this.Attributes &= ~SectionAttributes.MEM_READ;
        }
      }
    }
    public bool IsWritable
    {
      get
      {
        return (this.Attributes & SectionAttributes.MEM_WRITE) != 0;
      }
      set
      {
        if (value)
        {
          this.Attributes |= SectionAttributes.MEM_WRITE;
        }
        else
        {
          this.Attributes &= ~SectionAttributes.MEM_WRITE;
        }
      }
    }
    #endregion
    #region Constructors
    #endregion
    #region Methods
    #region Read/Write
    public SectionHeader Read(BinaryReader reader)
    {
      if (reader != null)
      {
        fixed (Byte* name = this.Name)
        {
          Utils.Read8(reader, name,SizeOfShortName);
        }
        this.VirtualSize = Utils.Read32(reader);
        this.VirtualAddress = Utils.Read32(reader);
        this.SizeOfRawData = Utils.Read32(reader);
        this.PointerToRawData = Utils.Read32(reader);
        this.PointerToRelocations = Utils.Read32(reader);
        this.PointerToLinenumbers = Utils.Read32(reader);
        this.NumberOfRelocations = Utils.Read16(reader);
        this.NumberOfLinenumbers = Utils.Read16(reader);
        this.Characteristics = Utils.Read32(reader);
      }
      return this;
    }
    public SectionHeader Write(BinaryWriter writer)
    {
      if (writer != null)
      {
        fixed (Byte* name = this.Name)
        {
          Utils.Write8(writer, name, SizeOfShortName);
        }
        Utils.Write32(writer, this.VirtualSize);
        Utils.Write32(writer, this.VirtualAddress);
        Utils.Write32(writer, this.SizeOfRawData);
        Utils.Write32(writer, this.PointerToRawData);
        Utils.Write32(writer, this.PointerToRelocations);
        Utils.Write32(writer, this.PointerToLinenumbers);
        Utils.Write16(writer, this.NumberOfRelocations);
        Utils.Write16(writer, this.NumberOfLinenumbers);
        Utils.Write32(writer, this.Characteristics);
      }
      return this;
    }
    #endregion
    #region Define Headers
    public SectionHeader Define(string sectionName, SectionAttributes attributes, UInt32 virtualAddress)
    {
      return this.Define(sectionName, attributes, virtualAddress, 0, 0, 0);
    }
    public SectionHeader Define(string sectionName, SectionAttributes attributes, UInt32 virtualAddress, UInt32 virtualSize, UInt32 rawPointer,UInt32 rawSize)
    {
      this.SectionName      = sectionName;
      this.Attributes       = attributes;
      this.VirtualAddress   = virtualAddress;
      this.LastAddress      = virtualAddress;
      this.VirtualSize      = virtualSize;
      this.PointerToRawData = rawPointer;
      this.SizeOfRawData    = rawSize;

      this.PointerToRelocations = DefaultRelocationAddress;
      this.NumberOfRelocations = DefaultRelocationCount;
      this.PointerToLinenumbers = DefaultLinenumberAddress;
      this.NumberOfLinenumbers = DefaultLinenumberCount;

      return this;
    }
    #endregion
    #region Space arrange
    public UInt32 Arrage(UInt32 length,UInt32 virtualAlign)
    {
      UInt32 alignLength = Utils.Align(length,virtualAlign);

      return 0;
    }
    #endregion
    #region Convertion
    public bool ContainsAddress(UInt32 RVA)
    {
      return RVA >= this.VirtualAddress && RVA < this.VirtualAddress + this.VirtualSize;
    }
    public UInt32 GetFilePointer(UInt32 RVA)
    {
      UInt32 filePointer = 0;

      if (this.GetFilePointer(RVA,ref filePointer))
      {
        return filePointer;
      }

      return 0;
    }
    public bool GetFilePointer(UInt32 RVA,ref UInt32 pointer)
    {
      if (this.ContainsAddress(RVA))
      {
        pointer = this.PointerToRawData + (RVA - this.VirtualAddress);
        return true;
      }
      return false;
    }
    #endregion
    #region ToStrings
    public override string ToString()
    {
      StringBuilder builder = new StringBuilder();

      builder.AppendFormat("SectionName:            {0}\r\n",    this.SectionName);
      builder.AppendFormat("VirtualSize             {0:X8}\r\n", this.VirtualSize);
      builder.AppendFormat("VirtualAddress          {0:X8}\r\n", this.VirtualAddress);
      builder.AppendFormat("PointerToRawData:       {0:X8}\r\n", this.PointerToRawData);
      builder.AppendFormat("PointerToRelocations:   {0:X8}\r\n", this.PointerToRelocations);
      builder.AppendFormat("PointerToLinenumbers:   {0:X8}\r\n", this.PointerToLinenumbers);
      builder.AppendFormat("NumberOfRelocations:    {0:X8}\r\n", this.NumberOfRelocations);
      builder.AppendFormat("NumberOfLinenumbers:    {0:X8}\r\n", this.NumberOfLinenumbers);
      builder.AppendFormat("Characteristics:        {0:X8}\r\n", this.Characteristics);
      builder.AppendFormat("Attributes:             {0}\r\n",    this.Attributes);

      return builder.ToString();
    }
    #endregion
    #endregion
  }
}
